﻿using System;
using System.Collections;

namespace CI.Organism
{
    /// <summary>
    /// Controls mutation probablity
    /// refactored using code from http://stackoverflow.com/questions/677373/generate-random-values-in-c/677390#677390
    /// </summary>
    public static class Mutation
    {
        // random
        private static readonly Random Random = new Random();
        private static readonly SimEnv SimEnv = SimEnv.Instance;

        /// <summary>
        /// Do we mutate with the current state, calculates to 2 decimal places
        /// </summary>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>True if we should mutate</returns>
        public static bool MutateState(double mutationRate)
        {
            // do we mutate
            double rndResult = Random.NextDouble();

            // if the mutation rate larger then the random result
            bool mutate = mutationRate > rndResult;

            if (mutate)
            {
                SimEnv.TraceLogger.LogDebug("Mutating");
            }

            // return the result
            return mutate;
        }

        /// <summary>
        /// Do we mutate 50% chance?
        /// </summary>
        /// <returns></returns>
        public static bool Mutate5050()
        {
            return MutateState(0.5);
        }

        /// <summary>
        /// Randomly mutate the value
        /// </summary>
        /// <param name="mutateValue">UInt32 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static UInt32 Mutate(UInt32 mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                mutateValue = GetMutateUInt32();
            }

            return mutateValue;
        }

        private static uint GetMutateUInt32()
        {
            var buffer = new byte[sizeof(UInt32)];
            Random.NextBytes(buffer);
            uint mutateValue = BitConverter.ToUInt32(buffer, 0);
            return mutateValue;
        }

        /// <summary>
        /// Randomly mutate the value, it may crawl and move by only 1 or leap to a completely new one
        /// </summary>
        /// <param name="mutateValue">int64 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static UInt32 MutateCrawlOrLeap(UInt32 mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                if (Mutate5050())
                {
                    // leap
                    mutateValue = GetMutateUInt32();
                }
                else
                {
                    //crawl
                    mutateValue += 1;
                }
            }

            return mutateValue;
        }

        /// <summary>
        /// Randomly mutate the value
        /// </summary>
        /// <param name="mutateValue">Int32 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static Int32 Mutate(Int32 mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                mutateValue = GetMutateInt32();
            }

            return mutateValue;
        }

        private static int GetMutateInt32()
        {
            var buffer = new byte[sizeof(Int32)];
            Random.NextBytes(buffer);
            int mutateValue = BitConverter.ToInt32(buffer, 0);
            return mutateValue;
        }

        /// <summary>
        /// Randomly mutate the value, it may crawl and move by only 1 or leap to a completely new one
        /// </summary>
        /// <param name="mutateValue">int64 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static Int32 MutateCrawlOrLeap(Int32 mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                if (Mutate5050())
                {
                    // leap
                    mutateValue = GetMutateInt32();
                }
                else
                {
                    //crawl
                    mutateValue += GetDirectionChange();
                }
            }

            return mutateValue;
        }

        private static int GetDirectionChange()
        {
            var directionChange = 1;

            if (Mutate5050())
                directionChange *= -1;
            return directionChange;
        }

        /// <summary>
        /// Randomly mutate the value
        /// </summary>
        /// <param name="mutateValue">int64 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static Int64 Mutate(Int64 mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                mutateValue = GetMutateInt64();
            }

            return mutateValue;
        }

        /// <summary>
        /// Randomly mutate the value, it may crawl and move by only 1 or leap to a completely new one
        /// </summary>
        /// <param name="mutateValue">int64 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static Int64 MutateCrawlOrLeap(Int64 mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                if (Mutate5050())
                {
                    // leap
                    mutateValue = GetMutateInt64();
                }
                else
                {
                    //crawl
                    mutateValue += GetDirectionChange();

                }
            }

            return mutateValue;
        }

        private static long GetMutateInt64()
        {
            var buffer = new byte[sizeof(Int64)];
            Random.NextBytes(buffer);
            var mutateValue = BitConverter.ToInt64(buffer, 0);
            return mutateValue;
        }

        /// <summary>
        /// Randomly mutate the value
        /// </summary>
        /// <param name="mutateValue">double value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static double Mutate(double mutateValue, double mutationRate)
        {
            // we mutate
            if (MutateState(mutationRate))
                mutateValue = Random.NextDouble();

            return mutateValue;
        }

        /// <summary>
        /// Randomly mutate the value, it may crawl and move by only 1 or leap to a completely new one
        /// </summary>
        /// <param name="mutateValue">int64 value to be mutated</param>
        /// <param name="mutationRate">Mutation Rate eg, 7.83% chance</param>
        /// <returns>new mutated value</returns>
        public static double MutateCrawlOrLeap(double mutateValue, double mutationRate)
        {
            if (MutateState(mutationRate))
            {
                if (Mutate5050())
                {
                    // leap
                    mutateValue = Random.NextDouble();
                }
                else
                {
                    //crawl
                    mutateValue += GetDirectionChange();
                }
            }

            return mutateValue;
        }

        /// <summary>
        /// Randomly select a value from the list passed
        /// </summary>
        /// <param name="collection">Collecton to select the index from</param>
        /// <returns>index of selected item</returns>
        public static int CollectionSelect(ICollection collection)
        {
            return Random.Next(collection.Count);
        }


        /// <summary>
        /// Randomly select a value within a specified range
        /// </summary>
        /// <param name="minValue">min value eg. 0</param>
        /// <param name="maxValue">max value eg. 100</param>
        /// <returns>int</returns>
        public static int RangeSelect(int minValue, int maxValue)
        {
            return Random.Next(minValue, maxValue);
        }


    }
}
